[PATCH] lib: Preserve errno in our malloc() and free() wrappers
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Thu, 26 Feb 2026 10:29:12 +0000 (12:29 +0200)
committerNoah Meyerhans <noahm@debian.org>
Thu, 5 Mar 2026 01:08:14 +0000 (20:08 -0500)
Various places assume that e.g. t_strdup_printf() calls and such don't
modify errno. But because they internally call malloc() or calloc(), this
isn't actually guaranteed now and it can happen at least with newer glibc
versions. Explicitly preserve the errno for these calls where it might
be a problem.

Gbp-Pq: Name lib_Preserve_errno_in_our_malloc_and_free_wrappers.patch

src/lib/data-stack.c
src/lib/mempool-allocfree.c
src/lib/mempool-alloconly.c
src/lib/mempool-system.c

index 477ad91d4a3c26123593762496655ec716fa87c4..fd29d13e38d285ab241117af7ad8264674439acc 100644 (file)
@@ -214,6 +214,7 @@ static void block_canary_check(struct stack_block *block)
 static void free_blocks(struct stack_block *block)
 {
        struct stack_block *next;
+       int old_errno = errno;
 
        /* free all the blocks, except if any of them is bigger than
           unused_block, replace it */
@@ -237,6 +238,7 @@ static void free_blocks(struct stack_block *block)
 
                block = next;
        }
+       errno = old_errno;
 }
 
 #ifdef DEBUG
@@ -367,6 +369,7 @@ static struct stack_block *mem_block_alloc(size_t min_size)
 {
        struct stack_block *block;
        size_t prev_size, alloc_size;
+       int old_errno = errno;
 
        prev_size = current_block == NULL ? 0 : current_block->size;
        /* Use INITIAL_STACK_SIZE without growing it to nearest power. */
@@ -386,6 +389,7 @@ static struct stack_block *mem_block_alloc(size_t min_size)
                i_panic("data stack: Out of memory when allocating %zu bytes",
                        alloc_size + SIZEOF_MEMBLOCK);
        }
+       errno = old_errno;
        block->size = alloc_size;
        block->canary = BLOCK_CANARY;
        mem_block_reset(block);
@@ -457,9 +461,7 @@ static void *t_malloc_real(size_t size, bool permanent)
        void *ret;
        size_t alloc_size;
        bool warn = FALSE;
-#ifdef DEBUG
        int old_errno = errno;
-#endif
 
        if (unlikely(size == 0 || size > SSIZE_T_MAX))
                i_panic("Trying to allocate %zu bytes", size);
@@ -519,17 +521,9 @@ static void *t_malloc_real(size_t size, bool permanent)
                current_block->left -= alloc_size;
 
        if (warn) T_BEGIN {
-               /* sending event can cause errno changes. */
-#ifdef DEBUG
-               i_assert(errno == old_errno);
-#else
-               int old_errno = errno;
-#endif
                /* warn after allocation, so if e_debug() wants to
                   allocate more memory we don't go to infinite loop */
                data_stack_send_grow_event(alloc_size);
-               /* reset errno back to what it was */
-               errno = old_errno;
        } T_END;
 #ifdef DEBUG
        memcpy(ret, &size, sizeof(size));
@@ -538,10 +532,8 @@ static void *t_malloc_real(size_t size, bool permanent)
           had used t_buffer_get(). */
        memset(PTR_OFFSET(ret, size), CLEAR_CHR,
               MEM_ALIGN(size + SENTRY_COUNT) - size);
-
-       /* we rely on errno not changing. it shouldn't. */
-       i_assert(errno == old_errno);
 #endif
+       errno = old_errno;
        return ret;
 }
 
@@ -739,8 +731,10 @@ size_t data_stack_get_used_size(void)
 
 void data_stack_free_unused(void)
 {
+       int old_errno = errno;
        free(unused_block);
        unused_block = NULL;
+       errno = old_errno;
 }
 
 void data_stack_init(void)
index 239db03e0e89713f3cfcc2cac2c67ee2a5ecb228..b2f6f277e7ef1c6572f6b0fd2cbe5df7c5908bb3 100644 (file)
@@ -146,6 +146,7 @@ static const struct pool static_allocfree_pool = {
 pool_t pool_allocfree_create(const char *name ATTR_UNUSED)
 {
        struct allocfree_pool *pool;
+       int old_errno = errno;
 
        (void) COMPILE_ERROR_IF_TRUE(SIZEOF_POOLBLOCK >
                                     (SSIZE_T_MAX - POOL_MAX_ALLOC_SIZE));
@@ -154,6 +155,7 @@ pool_t pool_allocfree_create(const char *name ATTR_UNUSED)
        if (pool == NULL)
                i_fatal_status(FATAL_OUTOFMEM, "calloc(1, %zu): Out of memory",
                               SIZEOF_ALLOCFREE_POOL);
+       errno = old_errno;
 #ifdef DEBUG
        pool->name = strdup(name);
 #endif
@@ -175,6 +177,8 @@ pool_t pool_allocfree_create_clean(const char *name)
 
 static void pool_allocfree_destroy(struct allocfree_pool *apool)
 {
+       int old_errno = errno;
+
        pool_allocfree_clear(&apool->pool);
        if (apool->clean_frees)
                safe_memset(apool, 0, SIZEOF_ALLOCFREE_POOL);
@@ -182,6 +186,7 @@ static void pool_allocfree_destroy(struct allocfree_pool *apool)
        free(apool->name);
 #endif
        free(apool);
+       errno = old_errno;
 }
 
 static const char *pool_allocfree_get_name(pool_t pool ATTR_UNUSED)
@@ -256,11 +261,13 @@ static void *pool_allocfree_malloc(pool_t pool, size_t size)
 {
        struct allocfree_pool *apool =
                container_of(pool, struct allocfree_pool, pool);
+       int old_errno = errno;
 
        struct pool_block *block = calloc(1, SIZEOF_POOLBLOCK + size);
        if (block == NULL)
                i_fatal_status(FATAL_OUTOFMEM, "calloc(1, %zu): Out of memory",
                               SIZEOF_POOLBLOCK + size);
+       errno = old_errno;
        block->size = size;
        return pool_block_attach(apool, block);
 }
@@ -269,10 +276,12 @@ static void pool_allocfree_free(pool_t pool, void *mem)
 {
        struct allocfree_pool *apool =
                container_of(pool, struct allocfree_pool, pool);
+       int old_errno = errno;
        struct pool_block *block = pool_block_detach(apool, mem);
        if (apool->clean_frees)
                safe_memset(block, 0, SIZEOF_POOLBLOCK+block->size);
        free(block);
+       errno = old_errno;
 }
 
 static void *pool_allocfree_realloc(pool_t pool, void *mem,
@@ -281,6 +290,7 @@ static void *pool_allocfree_realloc(pool_t pool, void *mem,
        struct allocfree_pool *apool =
                container_of(pool, struct allocfree_pool, pool);
        unsigned char *new_mem;
+       int old_errno = errno;
 
        struct pool_block *block = pool_block_detach(apool, mem);
        if (old_size == SIZE_MAX)
@@ -288,6 +298,7 @@ static void *pool_allocfree_realloc(pool_t pool, void *mem,
        if ((new_mem = realloc(block, SIZEOF_POOLBLOCK+new_size)) == NULL)
                i_fatal_status(FATAL_OUTOFMEM, "realloc(block, %zu)",
                               SIZEOF_POOLBLOCK+new_size);
+       errno = old_errno;
 
        /* zero out new memory */
        if (new_size > old_size)
index 79823dc2040750408b97f6b6e525aa5eb91df507..e1b0925bfc41d523dffc5aad421b0e8ec1d449f5 100644 (file)
@@ -281,6 +281,8 @@ pool_t pool_alloconly_create_clean(const char *name, size_t size)
 static void pool_alloconly_free_block(struct alloconly_pool *apool ATTR_UNUSED,
                                      struct pool_block *block)
 {
+       int old_errno = errno;
+
 #ifdef DEBUG
        safe_memset(block, CLEAR_CHR, SIZEOF_POOLBLOCK + block->size);
 #else
@@ -290,6 +292,7 @@ static void pool_alloconly_free_block(struct alloconly_pool *apool ATTR_UNUSED,
        }
 #endif
        free(block);
+       errno = old_errno;
 }
 
 static void
@@ -381,11 +384,13 @@ static void block_alloc(struct alloconly_pool *apool, size_t size)
 #endif
        }
 
+       int old_errno = errno;
        block = calloc(size, 1);
        if (unlikely(block == NULL)) {
                i_fatal_status(FATAL_OUTOFMEM, "block_alloc(%zu"
                               "): Out of memory", size);
        }
+       errno = old_errno;
        block->prev = apool->block;
        apool->block = block;
 
index 7d1addcaa60adf1d122e3bd27a0462030d3abbcb..e4c0134cc57ae6db05077c093e2c3891ccbb39df 100644 (file)
@@ -98,35 +98,27 @@ static void pool_system_unref(pool_t *pool ATTR_UNUSED)
 static void *pool_system_malloc(pool_t pool ATTR_UNUSED, size_t size)
 {
        void *mem;
-#ifdef DEBUG
        int old_errno = errno;
-#endif
 
        mem = calloc(size, 1);
        if (unlikely(mem == NULL)) {
                i_fatal_status(FATAL_OUTOFMEM, "pool_system_malloc(%zu): "
                               "Out of memory", size);
        }
-#ifdef DEBUG
-       /* we rely on errno not changing. it shouldn't. */
-       i_assert(errno == old_errno);
-#endif
+       errno = old_errno;
        return mem;
 }
 
 void pool_system_free(pool_t pool ATTR_UNUSED, void *mem ATTR_UNUSED)
 {
-#ifdef DEBUG
        int old_errno = errno;
-#endif
+
 #if defined(HAVE_MALLOC_USABLE_SIZE) && defined(DEBUG)
-       safe_memset(mem, CLEAR_CHR, malloc_usable_size(mem));
+       if (mem != NULL)
+               safe_memset(mem, CLEAR_CHR, malloc_usable_size(mem));
 #endif
        free(mem);
-#ifdef DEBUG
-       /* we rely on errno not changing. it shouldn't. */
-       i_assert(errno == old_errno);
-#endif
+       errno = old_errno;
 }
 
 static void *pool_system_realloc(pool_t pool ATTR_UNUSED, void *mem,
@@ -137,11 +129,13 @@ static void *pool_system_realloc(pool_t pool ATTR_UNUSED, void *mem,
                 old_size <= malloc_usable_size(mem));
 #endif
 
+       int old_errno = errno;
        mem = realloc(mem, new_size);
        if (unlikely(mem == NULL)) {
                i_fatal_status(FATAL_OUTOFMEM, "pool_system_realloc(%zu): "
                               "Out of memory", new_size);
        }
+       errno = old_errno;
 
        if (old_size < new_size) {
                /* clear new data */